package com.tripleying.dogend.dnfipm.util;

import com.jfoenix.controls.JFXAlert;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXProgressBar;
import com.tripleying.dogend.dnfipm.App;
import com.tripleying.dogend.dnfipm.controller.MainController;
import com.tripleying.dogend.dnfipm.model.NPKItem;
import com.tripleying.dogend.dnfipm.original.ByteWArrayOriginal;
import com.tripleying.dogend.dnfipm.original.FileOriginal;
import java.awt.Point;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.image.Image;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Modality;
import javax.imageio.ImageIO;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.util.ByteArrayStream;

/**
 * NPK工具
 * @author Dogend
 */
public class NPKUtil {
    
    private static final Pattern dnfPr;
    private static final Pattern npkPr;
    private static final Pattern zipPr;
    
    static {
        String dnfPattern = "^sprite+[\\x00-\\x7F]*\\.NPK+$";
        String npkPattern = "^.+(\\.npk|\\.npk\\.disable)$";
        String zipPattern = "^.+(\\.zip|\\.rar|\\.7z)$";
        dnfPr = Pattern.compile(dnfPattern, Pattern.CASE_INSENSITIVE);
        npkPr = Pattern.compile(npkPattern, Pattern.CASE_INSENSITIVE);
        zipPr = Pattern.compile(zipPattern, Pattern.CASE_INSENSITIVE);
    }
    
    public static boolean filenameCheck(String name){
        return (!dnfPr.matcher(name).matches() && npkPr.matcher(name).matches());
    }
    
    public static boolean filenameCheckIgnoreDNFPattern(String name){
        return npkPr.matcher(name).matches();
    }
    
    public static void addNPKFiles(List<File> fl){
        long startTime = System.currentTimeMillis();
        JFXAlert alert = new JFXAlert(App.getStage());
        alert.initModality(Modality.APPLICATION_MODAL);
        alert.setOverlayClose(false);
        alert.setHideOnEscape(false);
        JFXDialogLayout layout = new JFXDialogLayout();
        layout.getStylesheets().add("styles/scroll.css");
        layout.setPrefWidth(600);
        alert.setContent(layout);
        Map<String, String> result = new LinkedHashMap();
        JFXProgressBar filePg = new JFXProgressBar(0);
        filePg.setPrefWidth(400);
        filePg.setTranslateY(10);
        JFXProgressBar zipPg = new JFXProgressBar(0);
        zipPg.setTranslateY(10);
        zipPg.setPrefWidth(400);
        Label title = new Label("正在添加补丁……");
        title.setFont(new Font(20));
        layout.setHeading(title);
        Label bodyTitle = new Label();
        HBox titleBox = new HBox(bodyTitle);
        Label fileLabel = new Label();
        HBox fileBox = new HBox(new Label("总文件: "), filePg, fileLabel);
        fileBox.setSpacing(15);
        Label zipLabel = new Label();
        HBox zipBox = new HBox(new Label("压缩包: "), zipPg, zipLabel);
        zipBox.setSpacing(15);
        ScrollPane sp = new ScrollPane();
        sp.setMinHeight(300);
        sp.setPrefHeight(300);
        sp.setPadding(new Insets(5, 5, 5, 5));
        sp.setHbarPolicy(ScrollBarPolicy.NEVER);
        TextFlow tf = new TextFlow();
        tf.setBackground(new Background(new BackgroundFill(Color.rgb(255, 255, 255, 0), new CornerRadii(0), new Insets(0))));
        tf.heightProperty().addListener((o) -> sp.setVvalue(1));
        sp.setContent(tf);
        VBox vb = new VBox(titleBox, fileBox, zipBox, sp);
        vb.setSpacing(5);
        layout.setBody(vb);
        JFXButton btn = new JFXButton("关闭");
        btn.getStyleClass().add("closeBtn");
        btn.setFont(new Font(16));
        btn.setOnAction(e -> alert.hideWithAnimation());
        btn.setDisable(true);
        layout.setActions(btn);
        alert.show();
        Task task = new Task() {
            @Override
            protected Object call() throws Exception {
                int now = 1;
                int all = fl.size();
                for(File f:fl){
                    String fname = f.getName();
                    String filePgLabel = now+"/"+all;
                    double fileProgress = 1.0*(now++)/all;
                    Platform.runLater(() -> {
                        filePg.setProgress(fileProgress);
                        fileLabel.setText(filePgLabel);
                        zipPg.setProgress(0);
                        zipLabel.setText("0/0");
                        bodyTitle.setText("当前文件: "+fname);
                    });
                    try{
                        if(filenameCheckIgnoreDNFPattern(fname)){
                            String msg = FileUtil.installNPK(new NPKItem(f.getName()), new FileOriginal(f));
                            Platform.runLater(() -> {
                                tf.getChildren().add(MessageUtil.createText(fname, msg));
                            });
                        }else if(zipPr.matcher(fname).matches()){
                            try (RandomAccessFile raf = new RandomAccessFile(f.getPath(), "r"); IInArchive iia = SevenZip.openInArchive(null, new RandomAccessFileInStream(raf))) {
                                Platform.runLater(() -> {
                                    tf.getChildren().add(MessageUtil.createText("压缩包开始", "-----"+fname));
                                });
                                int[] in = new int[iia.getNumberOfItems()];
                                int len = in.length;
                                for (int i = 0; i < len; i++) {
                                    double zipProgress = 1.0*(i+1)/len;
                                    String zipPgLabel = (i+1)+"/"+len;
                                    Platform.runLater(() -> {
                                        zipPg.setProgress(zipProgress);
                                        zipLabel.setText(zipPgLabel);
                                    });
                                    String path = iia.getStringProperty(i, PropID.PATH);
                                    String zname = path.contains("\\")?path.substring(path.lastIndexOf('\\')+1):path;
                                    if(filenameCheckIgnoreDNFPattern(zname)){
                                        List<byte[]> dl = new ArrayList();
                                        iia.extractSlow(i, d -> {
                                            dl.add(d);
                                            return d.length;
                                        });
                                        byte[][] data = new byte[dl.size()][];
                                        dl.toArray(data);
                                        String msg = FileUtil.installNPK(new NPKItem(zname), new ByteWArrayOriginal(data));
                                        Platform.runLater(() -> {
                                            tf.getChildren().add(MessageUtil.createText(zname, msg));
                                        });
                                    }else if(zipPr.matcher(zname).matches()){
                                        List<byte[]> dl = new ArrayList();
                                        iia.extractSlow(i, d -> {
                                            dl.add(d);
                                            return d.length;
                                        });
                                        byte[][] data = new byte[dl.size()][];
                                        dl.toArray(data);
                                        ByteArrayStream bas = new ByteArrayStream(dl.stream().map(d -> d.length).reduce(0, Integer::sum));
                                        for(byte[] d:data){
                                            bas.write(d);
                                        }
                                        try (IInArchive iiac = SevenZip.openInArchive(null, bas)) {
                                            unzipInZip(iiac, zname, tf);
                                        }
                                    }
                                }
                                Platform.runLater(() -> {
                                    tf.getChildren().add(MessageUtil.createText("压缩包结束", "-----"+fname));
                                });
                            }
                        }else{
                            Platform.runLater(() -> {
                                tf.getChildren().add(MessageUtil.createText(fname, "× 无效的文件类型"));
                            });
                        }
                    }catch(Exception ex){
                        ex.printStackTrace();
                        Platform.runLater(() -> {
                            tf.getChildren().add(MessageUtil.createText(fname, "× 补丁添加失败, 异常的错误"));
                        });
                    }
                }
                Platform.runLater(() -> {
                    MainController.getInstance().loadNPK();
                    title.setText("补丁添加完成!");
                    Text txt = MessageUtil.createText("", "√");
                    txt.setText("一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一\n");
                    tf.getChildren().add(txt);
                    long endTime = System.currentTimeMillis();
                    long time = endTime - startTime;
                    tf.getChildren().add(MessageUtil.createText("补丁添加完成, 耗时", time + "ms"));
                    btn.setDisable(false);
                });
                return result;
            }
        };
        new Thread(task).start();
    }
    
    private static void unzipInZip(IInArchive iia, String fname, TextFlow tf) throws Exception{
        Platform.runLater(() -> {
            tf.getChildren().add(MessageUtil.createText("子压缩包开始", "-----"+fname));
        });
        int[] in = new int[iia.getNumberOfItems()];
        int len = in.length;
        for (int i = 0; i < len; i++) {
            String path = iia.getStringProperty(i, PropID.PATH);
            String zname = path.contains("\\")?path.substring(path.lastIndexOf('\\')+1):path;
            if(filenameCheckIgnoreDNFPattern(zname)){
                List<byte[]> dl = new ArrayList();
                iia.extractSlow(i, d -> {
                    dl.add(d);
                    return d.length;
                });
                byte[][] data = new byte[dl.size()][];
                dl.toArray(data);
                String msg = FileUtil.installNPK(new NPKItem(zname), new ByteWArrayOriginal(data));
                Platform.runLater(() -> {
                    tf.getChildren().add(MessageUtil.createText(zname, msg));
                });
            }else if(zipPr.matcher(zname).matches()){
                List<byte[]> dl = new ArrayList();
                iia.extractSlow(i, d -> {
                    dl.add(d);
                    return d.length;
                });
                ByteArrayStream bas = new ByteArrayStream(dl.stream().map(d -> d.length).reduce(0, Integer::sum));
                dl.forEach(d -> bas.write(d));
                try (IInArchive iiac = SevenZip.openInArchive(null, bas)) {
                    unzipInZip(iiac, zname, tf);
                }
            }
        }
        Platform.runLater(() -> {
            tf.getChildren().add(MessageUtil.createText("子压缩包结束", "-----"+fname));
        });
    }
    
    public static Image previewNPK(File file) throws FileNotFoundException, IOException{
        try{
            try(RandomAccessFile raf = new RandomAccessFile(file, "r")){
                NPK_Header header = new NPK_Header(raf);
                if(header.flag.contains("NeoplePack_Bill")){
                    NPK_Index index = new NPK_Index(raf);
                    if(!index.name.contains("守护")){
                        Img_Header imgh = new Img_Header(raf, index);
                        if(imgh.flag.contains("Neople Img File") || imgh.version==2){
                            raf.seek(index.offset+header.count*32);
                            NImgF_Index img_index = new NImgF_Index(raf);
                            raf.seek(index.offset + imgh.index_size + 32);
                            if(img_index.dwType != 0x11){
                                int size = img_index.size;
                                if (img_index.dwCompress == 0x05) {
                                    if (img_index.dwType == 0x10) {
                                        size = img_index.size;
                                    } else if (img_index.dwType == 0x0e || img_index.dwType == 0x0f) {
                                        size = img_index.size / 2;
                                    }
                                }
                                int buffer_size = 1024 * 1024 * 3;
                                byte[] temp_file_data = new byte[buffer_size];
                                raf.read(temp_file_data, 0, size);
                                byte[] data;
                                if(img_index.dwCompress == 0x06){
                                    try{
                                        data = ZLibUtil.decompress(temp_file_data);
                                    }catch(Exception e){
                                        e.printStackTrace();
                                        return null;
                                    }
                                }else{
                                    data = temp_file_data;
                                }
                                int height = img_index.height;
                                int width = img_index.width;
                                byte[][] row_pointers = new byte[height][width*4];  
                                byte[] pointers = new byte[height*width*4];
                                int k = 0;
                                for(int i = 0; i < img_index.height; i++){
                                    for(int j = 0; j < width; ++j){
                                        switch (img_index.dwType){
                                                case 0x0e://1555
                                                        row_pointers[i][j * 4 + 0] = (byte)(((data[i * width * 2 + j * 2 + 1] & 127) >> 2) << 3);   // red  
                                                        row_pointers[i][j * 4 + 1] = (byte)((((data[i * width * 2 + j * 2 + 1] & 0x0003) << 3) | ((data[i * width * 2 + j * 2] >> 5) & 0x0007)) << 3); // green  
                                                        row_pointers[i][j * 4 + 2] = (byte)((data[i * width * 2 + j * 2] & 0x003f) << 3); // blue 
                                                        row_pointers[i][j * 4 + 3] = (byte)((data[i * width * 2 + j * 2 + 1] >> 7) == 0 ? 0 : 255); // alpha
                                                        break;
                                                case 0x0f://4444
                                                        row_pointers[i][j * 4 + 0] = (byte)((data[i * width * 2 + j * 2 + 1] & 0x0f) << 4);   // red  
                                                        row_pointers[i][j * 4 + 1] = (byte)(((data[i * width * 2 + j * 2 + 0] & 0xf0) >> 4) << 4); // green  
                                                        row_pointers[i][j * 4 + 2] = (byte)((data[i * width * 2 + j * 2 + 0] & 0x0f) << 4);; // blue  
                                                        row_pointers[i][j * 4 + 3] = (byte)(((data[i * width * 2 + j * 2 + 1] & 0xf0) >> 4) << 4); // alpha
                                                        break;
                                                case 0x10://8888
                                                        row_pointers[i][j * 4 + 0] = data[i * width * 4 + j * 4 + 2]; // red
                                                        row_pointers[i][j * 4 + 1] = data[i * width * 4 + j * 4 + 1]; // green
                                                        row_pointers[i][j * 4 + 2] = data[i * width * 4 + j * 4 + 0]; // blue
                                                        row_pointers[i][j * 4 + 3] = data[i * width * 4 + j * 4 + 3]; // alpha
                                                        break;
                                                case 0x11:// 占位，无图片资源
                                                default:
                                                        return null;
                                        }
                                        pointers[k++] = row_pointers[i][j * 4 + 0];
                                        pointers[k++] = row_pointers[i][j * 4 + 1];
                                        pointers[k++] = row_pointers[i][j * 4 + 2];
                                        //pointers[k++] = row_pointers[i][j * 4 + 3];
                                    }
                                }
                                DataBuffer buffer = new DataBufferByte(pointers, pointers.length);
                                //3 bytes per pixel: red, green, blue
                                WritableRaster raster = Raster.createInterleavedRaster(buffer, img_index.width, img_index.height, 3 * img_index.width, 3, new int[] {0, 1, 2}, (Point)null);
                                ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, true, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); 
                                BufferedImage image = new BufferedImage(cm, raster, true, null);
                                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                                ImageIO.write(image, "png", baos);
                                byte[] imgData = baos.toByteArray();
                                ByteArrayInputStream bais = new ByteArrayInputStream(imgData);
                                Image img = new Image(bais);
                                return img;
                            }
                        }
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }
    
    public static int bytesToInt(byte[] a){
        int ans=0;
        for(int i=0;i<4;i++){
            ans<<=8;
            ans|=(a[3-i]&0xff);
        }
        return ans;
    }
    
}

class NPK_Header{
    byte[] flag_byte = new byte[16];
    byte[] count_byte = new byte[4];
    String flag; // 文件标识 "NeoplePack_Bill "
    int count; // 包内文件的数目
    
    NPK_Header(RandomAccessFile raf) throws IOException {
        raf.read(flag_byte);
        raf.read(count_byte);
        flag = new String(flag_byte);
        count = NPKUtil.bytesToInt(count_byte);
    }
};

class NPK_Index{
    byte[] offset_byte = new byte[4];
    byte[] size_byte = new byte[4];
    byte[] name_byte = new byte[256];
    int offset; // 文件的包内偏移量
    int size; // 文件的大小
    String name; // 文件名
    static byte[] decord_flag = new byte[256]; // 文件名解密字符串
    
    static {
        byte[] bytes = "puchikon@neople dungeon and fighter DNF".getBytes();
        int len = bytes.length;
        System.arraycopy(bytes, 0, decord_flag, 0, len);
	for (int i = len; i < 255; ++i) {
            switch ((i - len) % 3) {
                case 0:
                    decord_flag[i] = 'D';
                    break;
                case 1:
                    decord_flag[i] = 'N';
                    break;
                case 2:
                    decord_flag[i] = 'F';
                    break;
                default:
                    break;
            }
	}
	decord_flag[255] = '\0';
    }
    
    NPK_Index(RandomAccessFile raf) throws IOException {
        raf.read(offset_byte);
        raf.read(size_byte);
        raf.read(name_byte);
        offset = NPKUtil.bytesToInt(offset_byte);
        size = NPKUtil.bytesToInt(size_byte);
        for (int i = 0; i < 256; ++i) {
            name_byte[i] = (byte)(name_byte[i] ^ decord_flag[i]);
        }
        name = new String(name_byte);
        name = name.substring(0, name.indexOf('\0'));
    }
};

class Img_Header{
    byte[] flag_byte = new byte[16];
    byte[] index_size_byte = new byte[4];
    byte[] unknown1_byte = new byte[4];
    byte[] unknown2_byte = new byte[4];
    byte[] index_count_byte = new byte[4];
    String flag; // 文件标识"Neople Img File "
    int index_size; // 索引表大小，以字节为单位
    int unknown1;
    int version; // 图片版本号
    int index_count; // 索引表数目
    
    
    Img_Header(RandomAccessFile raf, NPK_Index index) throws IOException{
        raf.seek(index.offset);
        raf.read(flag_byte);
        flag = new String(flag_byte);
        if("Neople Img File ".equals(flag)){
            raf.read(index_size_byte);
            raf.read(unknown1_byte);
            raf.read(unknown2_byte);
            raf.read(index_count_byte);
            index_size = NPKUtil.bytesToInt(index_size_byte);
            unknown1 = NPKUtil.bytesToInt(unknown1_byte);
            version = NPKUtil.bytesToInt(unknown2_byte);
            index_count = NPKUtil.bytesToInt(index_count_byte);
        }else{
            System.out.println("不是 Neople Img File ");
        }
    }
};

class NImgF_Index{
    byte[] dwType_byte = new byte[4];
    byte[] dwCompress_byte = new byte[4];
    byte[] width_byte = new byte[4];
    byte[] height_byte = new byte[4];
    byte[] size_byte = new byte[4];
    byte[] key_x_byte = new byte[4];
    byte[] key_y_byte = new byte[4];
    byte[] max_width_byte = new byte[4];
    byte[] max_height_byte = new byte[4];
    int dwType; //目前已知的类型有 0x0E(1555格式) 0x0F(4444格式) 0x10(8888格式) 0x11(不包含任何数据，可能是指内容同上一帧)
    int dwCompress; // 目前已知的类型有 0x06(zlib压缩) 0x05(未压缩)
    int width; // 宽度
    int height; // 高度
    int size;  // 压缩时size为压缩后大小，未压缩时size为转换成8888格式时占用的内存大小
    int key_x; // X关键点，当前图片在整图中的X坐标
    int key_y; // Y关键点，当前图片在整图中的Y坐标
    int max_width; // 整图的宽度
    int max_height; // 整图的高度，有此数据是为了对齐精灵
    
    NImgF_Index(RandomAccessFile raf) throws IOException{
        raf.read(dwType_byte);
        raf.read(dwCompress_byte);
        dwType = NPKUtil.bytesToInt(dwType_byte);
        dwCompress = NPKUtil.bytesToInt(dwCompress_byte);
        if(dwType != 0x11){
            raf.read(width_byte);
            raf.read(height_byte);
            raf.read(size_byte);
            raf.read(key_x_byte);
            raf.read(key_y_byte);
            raf.read(max_width_byte);
            raf.read(max_height_byte);
            width = NPKUtil.bytesToInt(width_byte);
            height = NPKUtil.bytesToInt(height_byte);
            size = NPKUtil.bytesToInt(size_byte);
            key_x = NPKUtil.bytesToInt(key_x_byte);
            key_y = NPKUtil.bytesToInt(key_y_byte);
            max_width = NPKUtil.bytesToInt(max_width_byte);
            max_height = NPKUtil.bytesToInt(max_height_byte);
        }
    }
    
};